import type Easy from '.'
import type { EasyOptions } from '.'
import type { CustomContext, CustomState } from '..'
import type { Method } from './router'
import { FAILED_CODE } from './constant'
import { url2queryObj } from './utils'
import { validate as verify } from './verify'

export type Next = (arg?: any) => Promise<any>

export type Context<
  StateT = CustomState,
  CustomT = CustomContext,
> = BaseContext & {
  state: StateT
} & CustomT

export interface RequestOptions {
  method?: Method
  path: string
  url: string
  headers: Record<string, string>
  query: Record<string, any>
  querystring: string
  params: Record<string, any>
  body: Record<string, any> | string | null
  data: Record<string, any>
}
export interface ResponseOptions {
  status: number
  headers: Record<string, string>
  body: Record<string, any> | string | null
  send: (data: any, _code?: number) => void
  html: (data: any) => void
  text: (data: any) => void
  json: (data: any) => void
}
export interface BaseContext {
  event: {
    [name: string]: any
  }
  context: {
    [name: string]: any
  }
  config: EasyOptions
  /** easy框架请求参数 */
  request: RequestOptions
  /** 适配器请求参数,node环境下为原生请求对象,其它根据适配器返回响应的数据 */
  req: RequestOptions
  /** 请求url拼接的参数 */
  query?: Record<string, any>
  /** 获取如 /user/[id] 中id参数 */
  params?: Record<string, any>
  /** 如post提交的参数 */
  body?: Record<string, any>
  /** easy框架 响应参数 */
  response: ResponseOptions
  /** 适配器响应参数,node环境下为原生响应对象,其它根据适配器返回响应的数据 */
  res: ResponseOptions
  /** 设置响应头 ctx.set('Auth', 'Bearer xxx') */
  set: ((_field: { [key: string]: string }) => void) & ((_field: string, _val: string | string[]) => void) // http only
  /** 发送响应数据,框架内部自动判断数据类型适配响应头 */
  send: (_message: any, _code?: number) => void
  /** 响应html */
  html: (data: any) => void
  /** 响应text */
  text: (data: any) => void
  /** 响应json */
  json: (data: any) => void
  throw: (_code: string | number, _message: any) => any
  /** 取出对象中的指定字段，只支持对象第一层 */
  pick: (obj: Record<string, any>, keys: string | string[]) => Record<string, any>
  /** 排除对象中的指定字段，只支持对象第一层 */
  omit: (obj: Record<string, any>, keys: string | string[]) => Record<string, any>
  /** 将对象中的key下划线转为驼峰 */
  // toCamelCase: <T extends Record<string, any>>(obj: T) => Record<string, any>
  /** 获取当前时间戳 */
  timestamp: number
  verify: (data: any, schema: any) => { valid: boolean, data: any, errors: string[] }
  /** 扩展上下文，支持用户自定义设置的属性 */
  state: Record<string, any>
  /** easy框架 app对象 */
  app: Easy
}
interface Data { [key: string]: string }

class CustomError extends Error {
  code: number | string
  timestamp: string
  constructor(code: number | string, message: string) {
    super(message)
    this.name = 'CustomError'
    this.message = message
    this.code = code
    this.timestamp = new Date().toISOString()
  }
}
export const context = {
  response: {} as ResponseOptions,
  set(field: Data | string, val?: string | string[]) {
    if (arguments.length === 2) {
      if (Array.isArray(val)) {
        val = val.map(v => (typeof v === 'string' ? v : String(v))).join(';')
      }
      else if (typeof val !== 'string') {
        val = String(val)
      }
      this.response.headers[field as string] = val as string
    }
    else if (field && typeof field === 'object') {
      for (const key in field) {
        context.set(key, field[key])
      }
    }
  },
  throw: (code: string | number, message?: string) => {
    throw new CustomError(code, message || FAILED_CODE)
  },
  pick: (obj: Record<string, any> = {}, keys: string | string[] = '') => {
    if (typeof keys === 'string') keys = [keys]
    const temp: Record<string, any> = {}
    for (const key in obj) {
      if (keys.includes(key)) {
        temp[key] = obj[key]
      }
    }
    return temp
  },
  omit: (obj: Record<string, any> = {}, keys: string | string[] = '') => {
    if (typeof keys === 'string') keys = [keys]
    const temp: Record<string, any> = {}
    for (const key in obj) {
      if (!keys.includes(key)) {
        temp[key] = obj[key]
      }
    }
    return temp
  },
  verify,
  get timestamp() {
    return Date.now()
  },
  send(data: any, code: number = 200) {
    this.set('X-Powered-By', 'Easy')
    this.response.body = data
    this.response.status = code
  },
  html(data: string) {
    this.set('Content-Type', 'text/html;charset=utf-8')
    this.send(data)
  },
  text(data: string) {
    this.set('Content-Type', 'text/plain;charset=utf-8')
    this.send(data)
  },
  json(data: any) {
    this.set('Content-Type', 'application/json;charset=utf-8')
    this.send(data)
  },
}

export const request = {
  req: {} as RequestOptions,
  get url() {
    return this.req.url as string
  },
  set url(val) {
    this.req.url = val
  },
  get path() {
    return this.req.path as string
  },
  set path(val) {
    this.req.path = val
  },
  get originalUrl() {
    return this.req.url
  },
  set originalUrl(val) {
    this.url = val
  },

  get headers() {
    return this.req.headers
  },
  set headers(val) {
    this.req.headers = val
  },
  get header() {
    return this.headers
  },
  set header(val) {
    this.headers = val
  },

  get method() {
    return this.req.method || 'GET'
  },
  set method(val) {
    this.req.method = val
  },
  get query() {
    return this.req.query || url2queryObj(this.querystring)
  },

  set query(obj) {
    this.req.query = obj
  },
  get querystring() {
    return this.req.querystring
  },

  set querystring(obj) {
    this.req.querystring = obj
  },
  get params() {
    return this.req.params
  },

  set params(obj) {
    this.req.params = obj
  },
  get body() {
    return this.req.body
  },

  set body(obj) {
    this.req.body = obj
  },
  get data() {
    return this.body
  },

  set data(obj) {
    this.body = obj
  },
  get type() {
    const type = this.get('Content-Type')
    if (!type) return ''
    return type.split(';')[0]
  },
  get(field: string) {
    const req = this.req
    switch (field = field.toLowerCase()) {
      case 'referer':
      case 'referrer':
        return req.headers.referrer || req.headers.referer || ''
      default:
        return req.headers[field] || ''
    }
  },
}
export const response = {
  status: 200,
  body: null,
  headers: {},
}
